package app.modules.security.business.service.role;

import app.modules.account.business.service.UserRoleRelationService;
import app.modules.common.constant.CommonLockConstants;
import app.modules.common.enums.EffectiveFlagEnum;
import app.modules.security.business.service.SyncRbacCacheService;
import app.modules.security.constant.SecurityLockConstants;
import app.modules.security.constant.SecurityMsgConstants;
import app.modules.security.dto.web.role.WebRoleDTO;
import app.modules.security.dto.web.role.WebRoleQueryDTO;
import app.modules.security.dto.web.role.WebRoleSaveDTO;
import app.modules.security.dto.web.role.WebRoleUpdateDTO;
import app.modules.security.persistence.dao.RoleDAO;
import app.modules.security.persistence.dao.RoleFunctionRelationDAO;
import app.modules.security.util.conversion.RoleConvertUtil;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import jasmine.framework.common.exception.BusinessException;
import jasmine.framework.common.exception.DataNotFoundException;
import jasmine.framework.common.util.CheckUtil;
import jasmine.framework.common.util.CollectionUtil;
import jasmine.framework.common.util.I18nUtil;
import jasmine.framework.common.util.MapperUtil;
import jasmine.framework.common.util.NewUtil;
import jasmine.framework.lock.annotation.DistributedLock;
import jasmine.security.rbac.model.SecRole;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.Collection;
import java.util.Collections;
import java.util.List;

/**
 * @author mh.z
 */
@Service
public class RoleService implements IRoleService {
    private RoleDAO roleDAO;
    private RoleFunctionRelationDAO roleFunctionRelationDAO;
    private UserRoleRelationService userRoleRelationService;
    private SyncRbacCacheService syncRbacCacheService;

    public RoleService(RoleDAO roleDAO,
                       RoleFunctionRelationDAO roleFunctionRelationDAO,
                       UserRoleRelationService userRoleRelationService,
                       SyncRbacCacheService syncRbacCacheService) {
        this.roleDAO = roleDAO;
        this.roleFunctionRelationDAO = roleFunctionRelationDAO;
        this.userRoleRelationService = userRoleRelationService;
        this.syncRbacCacheService = syncRbacCacheService;
    }

    /**
     * 保存角色
     *
     * @param saveDTO
     * @return
     */
    @DistributedLock(category = SecurityLockConstants.LOCK_CATEGORY_CRUD_ROLE, key = CommonLockConstants.LOCK_KEY_TENANT)
    @Transactional(rollbackFor = Exception.class)
    public WebRoleDTO saveRole(WebRoleSaveDTO saveDTO) {
        CheckUtil.notNull(saveDTO, "saveDTO null");

        String roleCode = saveDTO.getRoleCode();
        long roleCodeCount = roleDAO.countByRoleCode(roleCode, null);
        if (roleCodeCount > 0) {
            throw new BusinessException(SecurityMsgConstants.THE_ROLE_CODE_ALREADY_EXISTS,
                    NewUtil.array(roleCode));
        }

        SecRole secRole = new SecRole();
        MapperUtil.mapFields(saveDTO, secRole);
        // 保存记录
        roleDAO.save(secRole);

        return getWebRoleDtoById(secRole.getId());
    }

    /**
     * 更新角色
     *
     * @param updateDTO
     * @return
     */
    @DistributedLock(category = SecurityLockConstants.LOCK_CATEGORY_CRUD_ROLE, key = CommonLockConstants.LOCK_KEY_TENANT)
    @Transactional(rollbackFor = Exception.class)
    public WebRoleDTO updateRole(WebRoleUpdateDTO updateDTO) {
        CheckUtil.notNull(updateDTO, "updateDTO null");
        Long id = CheckUtil.notNull(updateDTO.getId(), "updateDTO.id null");

        String roleCode = updateDTO.getRoleCode();
        long roleCodeCount = roleDAO.countByRoleCode(roleCode, Collections.singletonList(id));
        if (roleCodeCount > 0) {
            throw new BusinessException(SecurityMsgConstants.THE_ROLE_CODE_ALREADY_EXISTS,
                    NewUtil.array(roleCode));
        }

        SecRole secRole = roleDAO.getById(id);
        if (secRole == null) {
            throw new DataNotFoundException(SecRole.class, id);
        }

        MapperUtil.mapFields(updateDTO, secRole);
        // 更新记录
        roleDAO.strictUpdateById(secRole);

        // 同步缓存
        syncRbacCacheService.syncFunctionsWithRoleIdCache(SecRole.class, secRole);

        return getWebRoleDtoById(id);
    }

    /**
     * 删除角色
     *
     * @param recordId
     */
    @DistributedLock(category = SecurityLockConstants.LOCK_CATEGORY_CRUD_ROLE, key = CommonLockConstants.LOCK_KEY_TENANT)
    @Transactional(rollbackFor = Exception.class)
    public void deleteRecordById(Long recordId) {
        CheckUtil.notNull(recordId, "recordId null");

        long count = userRoleRelationService.countByRoleId(recordId);
        if (count > 0) {
            throw new BusinessException(SecurityMsgConstants.CANNOT_DELETE_BECAUSE_ASSIGNED_TO_USER, null);
        }

        roleDAO.deleteById(recordId);
        roleFunctionRelationDAO.deleteByRoleId(recordId);

        // 同步缓存
        syncRbacCacheService.syncFunctionsWithRoleIdCache(SecRole.class, recordId);
    }

    public List<WebRoleDTO> pageWebRoleDTOsByCond(WebRoleQueryDTO queryDTO, Page page) {
        CheckUtil.notNull(queryDTO, "queryDTO null");
        CheckUtil.notNull(page, "page null");

        List<WebRoleDTO> recordList = roleDAO.pageWebRoleDTOsByCond(queryDTO, page);
        populateWebRoleDTOs(recordList);

        return recordList;
    }

    /**
     * 查找指定 ID 的记录
     *
     * @param id
     * @return
     */
    public WebRoleDTO getWebRoleDtoById(Long id) {
        CheckUtil.notNull(id, "id null");

        SecRole secRole = roleDAO.getById(id);
        WebRoleDTO webRoleDTO = RoleConvertUtil.secRole2WebRoleDTO(secRole);
        populateWebRoleDTOs(Collections.singletonList(webRoleDTO));

        return webRoleDTO;
    }

    @Override
    public List<SecRole> listByIds(Collection<Long> ids) {
        return roleDAO.listByIds(ids);
    }

    /**
     * 填充数据
     *
     * @param records
     */
    @Override
    public void populateWebRoleDTOs(List<WebRoleDTO> records) {
        if (CollectionUtil.isEmpty(records)) {
            return;
        }

        records.forEach((record) -> {
            EffectiveFlagEnum effectiveFlagEnum = EffectiveFlagEnum.parseValue(record.getEffectiveFlag());
            record.setEffectiveFlagDesc(I18nUtil.getMessage(effectiveFlagEnum));
        });
    }

}
